//
//  main.m
//  csImageResizer
//
//  Created by Jim Turner on 3/17/06.
//  Copyright The Des Moines Register 2006. All rights reserved.
//
// Scales images.  What fun!  Runs on 10.3.9 an higher.
// Note that on 10.3.9, you'll get the error:
//		kCGErrorFailure : GetProfileBySpaceFromShmem returned NULL for 'RRRR' : Calling Dispatcher.
//
// It's caused by our friend CGBitmapContextCreate().  I've not a clue as to why.  Just discard
// STDERR and you'll be right as rain.
//////////////////////////////////////////////////////////////////////////

#import <Foundation/Foundation.h>
#import <ApplicationServices/ApplicationServices.h>
#import <AppKit/AppKit.h>
#import <unistd.h>
#import <string.h>

// Enumerate some values for what type of image we are dealing with
enum {
	JTImageIsJPEG = 1,
	JTImageIsPNG,
	JTImageIsGIF,
	JTImageTypeIsUnknown
};

// Prototype our only "other" function
void _help( char *error );

int main (int argc, char * argv[]) 
{
	CGImageRef img;
	CGContextRef newImgSetup;
	CGImageAlphaInfo alphaInfo;
	CGColorSpaceRef colorSpace;
	size_t components;
	size_t width;
	size_t height;
	size_t bitsPerComponent;
	size_t bytesPerRow;
	size_t dataLength;
	void *data = NULL;
	char *pathToOutput = NULL;
	char *pathToFile = NULL;
	NSString *objcPathToFile;
	NSString *objcPathToOutput;
	int minWidth = 224;
	int imageType;
	int argSwitch;
	int verbose = 0;
	SInt32 OSVersion;

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

	while( (argSwitch = getopt( argc, argv, "i:o:w:hv" )) != -1 )
	{
		switch( argSwitch )
		{
			case 'i':
				pathToFile = malloc( strlen( optarg ) );
				strcpy( pathToFile, optarg );
				break;
			case 'o':
				pathToOutput = malloc( strlen( optarg ) );
				strcpy( pathToOutput, optarg );
				break;
			case 'w':
				minWidth = atoi( optarg );
				break;
			case 'v':
				verbose = 1;
				break;
			case '?':
			default:
				_help( NULL );
				return( EXIT_FAILURE );
		}
	}

	if( pathToFile == NULL )
	{
		_help( "-i must be defined" );
	}

	if( pathToOutput == NULL )
	{
		_help( "-o must be defined" );
	}

	if( Gestalt( gestaltSystemVersion, &OSVersion ) == noErr )
	{
		if( OSVersion >= 0x1040 )
		{
			if( verbose ) { printf( "OS: 10.4.x\n" ); }
			objcPathToFile   = [NSString stringWithCString:pathToFile encoding:NSUTF8StringEncoding];
			objcPathToOutput = [NSString stringWithCString:pathToOutput encoding:NSUTF8StringEncoding];
		}
		else if( OSVersion >= 0x1039 )
		{
			if( verbose ) { printf( "OS: 10.3.9\n" ); }
			objcPathToFile   = [NSString stringWithCString:pathToFile];
			objcPathToOutput = [NSString stringWithCString:pathToOutput];
		}
		else
		{
			printf( "os version [%X] not supported\n", OSVersion );
			exit(18);
		}
	}
	else
	{
		// Error getting the OS version... hrm.
		printf( "Unable to determine OS Version...\n" );
		exit(17);
	}

	// Does the source image exist?
	BOOL isDir;
	NSFileManager *fileMgr = [NSFileManager defaultManager];
	if( [fileMgr fileExistsAtPath:objcPathToFile isDirectory:&isDir] == YES )
	{
		// But is it a directory?
		if( isDir == YES )
		{
			_help( "-i must point to a single file, not a directory" );
		}
	}
	else
	{
		char *dne = malloc( strlen( pathToFile ) + 25 );
		sprintf( dne, "[%s] does not exist", pathToFile );
		_help( dne );
	}

	CFURLRef location = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, 
													   CFStringCreateWithCString( kCFAllocatorDefault, pathToFile, kCFStringEncodingUTF8 ),
													   kCFURLPOSIXPathStyle,
													   false );
													   
	if( location == NULL )
	{
		printf( "location is null?! That shouldn't happen...\n" );
		exit(-9);
	}
	
	// What type of image is this?  If we don't have an extension, we assume jpg
	NSRange lastDot = [objcPathToFile rangeOfString:@"." options:NSBackwardsSearch];
	if( lastDot.location == NSNotFound )
	{
		NSLog( @"file has no extension, assuming jpg" );
		imageType = JTImageIsJPEG;
	}
	else
	{
		NSString *extension = [objcPathToFile substringWithRange:NSMakeRange( lastDot.location + 1, [objcPathToFile length] - (lastDot.location + 1) )];
		if( [extension caseInsensitiveCompare:@"png"] == NSOrderedSame )
		{
			imageType = JTImageIsPNG;
		}
		else if( [extension caseInsensitiveCompare:@"gif"] == NSOrderedSame )
		{
			imageType = JTImageIsGIF;
		}
		else if( ([extension caseInsensitiveCompare:@"jpg"]  == NSOrderedSame) ||
				 ([extension caseInsensitiveCompare:@"jpeg"] == NSOrderedSame) )
		{
			imageType = JTImageIsJPEG;
		}
		else
		{
			imageType = JTImageTypeIsUnknown;
		}
	}

	// Catch weird filetypes
	if( imageType == JTImageTypeIsUnknown )
	{
		NSLog( @"Unable to determine file type for [%@]", objcPathToFile );
		exit(-1);
	}

	if( imageType == JTImageIsPNG )
	{
		NSLog( @"PNG images are not supported" );
		exit(1);
	
		// I don't know how to get a 3-component graphic with alpha information (which makes it 4 components) into CGCreateBitmapContext
		// when the image itself reports it only has 3 components, but also says "kCGImageAlphaLast"... very confusing.
		img = CGImageCreateWithPNGDataProvider( CGDataProviderCreateWithURL( location ), NULL, true, kCGRenderingIntentPerceptual );
		if( img == NULL )
		{
			// If img is not valid, most likely the problem exists in creating a proper CFURLRef.
			// Make sure the path being created is actually what CFURLCreateWithFileSystemPath() wants
			// and is actually of the string encoding type you chose.
			NSLog( @"cannot create png data provider!" );
			exit(1);
		}
	}
	else if( imageType == JTImageIsGIF )
	{
		NSLog( @"GIF images are not supported" );
		exit(1);
	}
	else
	{
		// Default to jpg
		img = CGImageCreateWithJPEGDataProvider( CGDataProviderCreateWithURL( location ), NULL, true, kCGRenderingIntentPerceptual );
		if( img == NULL )
		{
			// If img is not valid, most likely the problem exists in creating a proper CFURLRef.
			// Make sure the path being created is actually what CFURLCreateWithFileSystemPath() wants
			// and is actually of the string encoding type you chose.
			NSLog( @"cannot create jpeg data provider!" );
			exit(1);
		}
	}
	
if( verbose )
{
	printf( "width: %d\nheight: %d\nalphaInfo: %d\nbits per component: %d\nbits per pixel: %d\nbytes per row: %d\ncolor space: %p\nis mask: %d\n", 
																			  CGImageGetWidth( img ), CGImageGetHeight( img ), CGImageGetAlphaInfo( img ),
																			  CGImageGetBitsPerComponent( img ), CGImageGetBitsPerPixel(img), 
																			  CGImageGetBytesPerRow( img ), CGImageGetColorSpace( img ), CGImageIsMask( img ) );
}

	// Alpha info
	// https://developer.apple.com/documentation/GraphicsImaging/Reference/CGImage/Reference/reference.html#//apple_ref/c/tdef/CGImageAlphaInfo
	if( CGImageGetAlphaInfo( img ) == kCGImageAlphaNone )
	{
		alphaInfo = kCGImageAlphaNoneSkipLast;
	}
	else
	{
		alphaInfo = CGImageGetAlphaInfo( img );
	}
	
	// http://developer.apple.com/qa/qa2001/qa1037.html
//	colorSpace = CGColorSpaceCreateDeviceRGB();
	colorSpace = CGImageGetColorSpace( img );

	// Calculate the new image ratio
	// This does both shrinking and expanding
	float scalingOffset;

	scalingOffset = (float)minWidth / CGImageGetWidth( img );
	width         = minWidth;
	height        = CGImageGetHeight( img ) * scalingOffset;
		
	components       = 4;
	bitsPerComponent = CGImageGetBitsPerComponent( img );
	bytesPerRow      = (width * bitsPerComponent * components + 7)/8;
	dataLength       = bytesPerRow * height;

if( verbose )
{
	printf( "scaling offset: %lf\nbytesPerRow: %d\ndataLength: %d\n", scalingOffset, bytesPerRow, dataLength );
	printf( "Number of components: %d\n", CGColorSpaceGetNumberOfComponents( colorSpace ) );
	printf( "Setting target width to %d\n", minWidth );
}
	data = malloc( dataLength );
	
	if( data == nil )
	{
		NSLog( @"Failed to create a data buffer!" );
		exit(4);
	}

	// This guy is a pain in everyone's ass.  It creates the GetProfileBySpaceFromShmem error noted at the
	// top of this file when ran on 10.3.9
	newImgSetup = CGBitmapContextCreate( data, width, height, bitsPerComponent, bytesPerRow, colorSpace, alphaInfo );
	
	if( newImgSetup == nil )
	{
		NSLog( @"failed to create a bitmap context!" );
		exit(2);
	}
	
	// Set the quality, as it were
	CGContextSetInterpolationQuality( newImgSetup, kCGInterpolationHigh );
	
	// Draw the old jpeg into our new bitmap context
	CGContextDrawImage( newImgSetup, CGRectMake( 0, 0, width, height ), img );
	
	// Send it out to disk.
	// To do this, we need to read into NSData the contents of 'data' above, then 
	// convert it to NSBitmapImageRep.  CoreGraphics has no native mechanism for
	// writing a file to disk.
	unsigned char *imagePointer[1];
	
	NSData *imageData = [NSData dataWithBytesNoCopy:data length:dataLength freeWhenDone:NO];
	imagePointer[0]   = (unsigned char *)[imageData bytes];
	
	NSBitmapImageRep *imageBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:imagePointer
															pixelsWide:width
															pixelsHigh:height
															bitsPerSample:bitsPerComponent
															samplesPerPixel:4
															hasAlpha:YES
															isPlanar:NO
															colorSpaceName:NSDeviceRGBColorSpace
															bytesPerRow:(width * bitsPerComponent * 4 + 7)/8
															bitsPerPixel:0];
															
	if( imageBitmap == nil )
	{
		NSLog( @"Failed to create NSBitmapImageData!" );
		exit(3);
	}
	
	NSData *newRep;
	if( imageType == JTImageIsJPEG )
	{
		newRep = [imageBitmap representationUsingType:NSJPEGFileType properties:nil];
	}
	else if( imageType == JTImageIsPNG )
	{
		newRep = [imageBitmap representationUsingType:NSPNGFileType properties:nil];
	}
	
	if( [newRep writeToFile:objcPathToOutput atomically:NO] != YES )
	{
		printf( "Failed to write image to %s!  Does that path exist?\n", pathToOutput );
	}
	
	// Release objects and stuff
	[imageBitmap release];
	CGContextRelease( newImgSetup );
	CGImageRelease( img );

	
    [pool release];
    return 0;
}

void _help( char *error )
{
	if( error )
	{
		printf( "\n%s\n\n", error );
	}

	printf( "Usage: csImageResizer -i /path/to/input/file -o /path/to/output/file [-w minWidth] [-h] [-v]\n" );
	printf( "\t-i -> The original file to work with.  This can only be a jpg (for now)\n" );
	printf( "\t-o -> Where to place the resized image\n" );
	printf( "\t-w -> What width are we going for? (defaults to 224)\n" );
	printf( "\t-v -> Output some technical details\n" );
	printf( "\t-h -> This message\n\n" );
	
	if( error )
	{
		exit(99);
	}
	else
	{
		exit(0);
	}
}
